package bancosys.tec.utils.traverser;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Classe que define o algoritmo genérico de se percorrer um grafo de objetos.
 * 
 * @author pedro
 */
public abstract class AbstractTraverser {
    private List<? extends Visitor> visitors;

    /**
     * Construtur.
     */
    public AbstractTraverser() {
        // vazio.
    }

    /**
     * Construtor
     * 
     * @param visitors lista de visitors que visitarão os nós
     */
    public AbstractTraverser(List<? extends Visitor> visitors) {
        this.visitors = visitors;
    }

    /**
     * Define o valor para o campo visitors.
     * 
     * @param visitors o valor para o campo visitors.
     */
    public void setVisitors(List<? extends Visitor> visitors) {
        this.visitors = visitors;
    }

    /**
     * Percorre o grafo de objetos, visitando todos os elementos
     * 
     * @param root objeto a partir do qual é feita a visita
     * @throws TraverseException caso ocorra algum problema
     */
    public final void traverse(Object root) throws TraverseException {
        HashSet<Object> visitedNodes = new HashSet<Object>();
        this.doTraverse(root, visitedNodes);
    }

    /**
     * Método que efetivamente percorre o grafo de objetos recursivamente
     * 
     * @param root ponto a partir do qual o grafo de objetos será percorrido
     * @param visitedNodes indica quais entidades já foram visitas
     * @throws TraverseException caso ocorra algum probmela
     */
    private void doTraverse(Object root, Set<Object> visitedNodes) throws TraverseException {
        if (root == null || this.mustStopTraverse(root, visitedNodes)) {
            return;
        }

        if (!this.isCollectionType(root)) {
            for (Visitor visitor : this.visitors) {
                visitor.visit(root);
            }
            visitedNodes.add(root);
        }

        for (Object node : this.getLinkedNodes(root)) {
            this.doTraverse(node, visitedNodes);
        }
    }

    /**
     * Método que diz se deve parar a busca nesse ponto ou não Implementação apenas devolve <code>true</code> se o nó já foi visitado.
     * 
     * @param node nó que deve ou não ser visitado
     * @param visitedNodes conjunto de nós já visitados
     * @return se deve parar a busca nesse ponto ou não
     */
    protected boolean mustStopTraverse(Object node, Set<Object> visitedNodes) {
        return node != null ? visitedNodes.contains(node) : false;
    }

    /**
     * Devolve um Iterable de objetos relacionados.
     * 
     * @param node nó que está sendo visitado
     * @return um Iterable de objetos relacionados
     */
    protected abstract Iterable<Object> getLinkedNodes(Object node);

    /**
     * Diz se o node é um collection Type do Hibernate ou não.
     * 
     * @param node objeto a ser decidido se é um collectioType ou não
     * @return se é um collectionType
     */
    protected boolean isCollectionType(Object node) {
        return (node.getClass().isArray() || node instanceof Collection<?> || node instanceof Map<?, ?>) ? true : false;
    }
}
